home *** CD-ROM | disk | FTP | other *** search
/ Visual Cafe 3 / Visual Cafe 3.ISO / Vcafe / Sample.bin / Animator.java < prev    next >
Text File  |  1998-09-15  |  29KB  |  1,135 lines

  1. /*
  2.  * @(#)Animator.java    1.10 97/02/05
  3.  *
  4.  * Copyright (c) 1997 Sun Microsystems, Inc. All Rights Reserved.
  5.  *
  6.  * Sun grants you ("Licensee") a non-exclusive, royalty free, license to use,
  7.  * modify and redistribute this software in source and binary code form,
  8.  * provided that i) this copyright notice and license appear on all copies of
  9.  * the software; and ii) Licensee does not utilize the software in a manner
  10.  * which is disparaging to Sun.
  11.  *
  12.  * This software is provided "AS IS," without a warranty of any kind. ALL
  13.  * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
  14.  * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
  15.  * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
  16.  * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
  17.  * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
  18.  * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
  19.  * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
  20.  * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
  21.  * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
  22.  * POSSIBILITY OF SUCH DAMAGES.
  23.  *
  24.  * This software is not designed or intended for use in on-line control of
  25.  * aircraft, air traffic, aircraft navigation or aircraft communications; or in
  26.  * the design, construction, operation or maintenance of any nuclear
  27.  * facility. Licensee represents and warrants that it will not use or
  28.  * redistribute the Software for such purposes.
  29.  */
  30.  
  31. import java.awt.*;
  32. import java.awt.event.*;
  33. import java.applet.Applet;
  34. import java.applet.AudioClip;
  35. import java.util.Vector;
  36. import java.util.Hashtable;
  37. import java.util.Enumeration;
  38. import java.net.URL;
  39. import java.net.MalformedURLException;
  40.  
  41. /**
  42.  * An applet that plays a sequence of images, as a loop or a one-shot.
  43.  * Can have a soundtrack and/or sound effects tied to individual frames.
  44.  * See the <a href="http://java.sun.com/applets/applets/Animator/">Animator
  45.  * home page</a> for details and updates.
  46.  *
  47.  * @author Herb Jellinek
  48.  * @version 1.10, 02/05/97
  49.  */
  50.  
  51. public class Animator extends Applet
  52.                       implements Runnable,
  53.                  MouseListener {
  54.     
  55.     /**
  56.      * The images, in display order (Images).
  57.      */
  58.     Vector images = null;
  59.  
  60.     /**
  61.      * A table that maps images to image names - for error messages.
  62.      */
  63.     Hashtable imageNames = new Hashtable(10);
  64.  
  65.     /**
  66.      * Duration of each image (Integers, in milliseconds).
  67.      */
  68.     Hashtable durations = null;
  69.  
  70.     /**
  71.      * Sound effects for each image (AudioClips).
  72.      */
  73.     Hashtable sounds = null;
  74.  
  75.     /**
  76.      * Position of each image (Points).
  77.      */
  78.     Hashtable positions = null;
  79.  
  80.     /**
  81.      * MediaTracker 'class' ID numbers.
  82.      */
  83.     static final int STARTUP_ID    = 0;
  84.     static final int BACKGROUND_ID = 1;
  85.     static final int ANIMATION_ID  = 2;
  86.  
  87.     /**
  88.      * Start-up image URL, if any.
  89.      */
  90.     URL startUpImageURL = null;
  91.  
  92.     /**
  93.      * Start-up image, if any.
  94.      */
  95.     Image startUpImage = null;
  96.  
  97.     /**
  98.      * Background image URL, if any.
  99.      */
  100.     URL backgroundImageURL = null;
  101.  
  102.     /**
  103.      * Background image, if any.
  104.      */
  105.     Image backgroundImage = null;
  106.  
  107.     /**
  108.      * Background color, if any.
  109.      */
  110.     Color backgroundColor = null;
  111.     
  112.     /**
  113.      * The soundtrack's URL.
  114.      */
  115.     URL soundtrackURL = null;
  116.  
  117.     /**
  118.      * The soundtrack.
  119.      */
  120.     AudioClip soundtrack = null;
  121.  
  122.     /**
  123.      * URL to link to, if any.
  124.      */
  125.     URL hrefURL = null;
  126.  
  127.     /**
  128.      * Frame target for that URL, if any.
  129.      */
  130.     String hrefTarget = null;
  131.  
  132.     /**
  133.      * Our width.
  134.      */
  135.     int appWidth = 0;
  136.  
  137.     /**
  138.      * Our height.
  139.      */
  140.     int appHeight = 0;
  141.  
  142.     /**
  143.      * The directory or URL from which the images are loaded
  144.      */
  145.     URL imageSource = null;
  146.  
  147.     /**
  148.      * The directory or URL from which the sounds are loaded
  149.      */
  150.     URL soundSource = null;
  151.  
  152.     /**
  153.      * The thread animating the images.
  154.      */
  155.     Thread engine = null;
  156.  
  157.     /**
  158.      * The current loop slot - index into 'images.'
  159.      */
  160.     int frameNum;
  161.  
  162.     /**
  163.      * frameNum as an Object - suitable for use as a Hashtable key.
  164.      */
  165.     Integer frameNumKey;
  166.     
  167.     /**
  168.      * The current X position (for painting).
  169.      */
  170.     int xPos = 0;
  171.     
  172.     /**
  173.      * The current Y position (for painting).
  174.      */
  175.     int yPos = 0;
  176.     
  177.     /**
  178.      * The default number of milliseconds to wait between frames.
  179.      */
  180.     public static final int defaultPause = 3900;
  181.     
  182.     /**
  183.      * The global delay between images, which can be overridden by
  184.      * the PAUSE parameter.
  185.      */
  186.     int globalPause = defaultPause;
  187.  
  188.     /**
  189.      * Whether or not the thread has been paused by the user.
  190.      */
  191.     boolean userPause = false;
  192.  
  193.     /**
  194.      * Repeat the animation?  If false, just play it once.
  195.      */
  196.     boolean repeat;
  197.  
  198.     /**
  199.      * The offscreen image, used in double buffering
  200.      */
  201.     Image offScrImage;
  202.  
  203.     /**
  204.      * The offscreen graphics context, used in double buffering
  205.      */
  206.     Graphics offScrGC;
  207.  
  208.     /**
  209.      * The MediaTracker we use to load our images.
  210.      */
  211.     MediaTracker tracker;
  212.     
  213.     /**
  214.      * Can we paint yet?
  215.      */
  216.     boolean loaded = false;
  217.  
  218.     /**
  219.      * Was there an initialization error?
  220.      */
  221.     boolean error = false;
  222.  
  223.     /**
  224.      * What we call an image file in messages.
  225.      */
  226.     static final String imageLabel = "image";
  227.     
  228.     /**
  229.      * What we call a sound file in messages.
  230.      */
  231.     static final String soundLabel = "sound";
  232.     
  233.     /**
  234.      * Print silly debugging info?
  235.      */
  236.     static final boolean debug = false;
  237.  
  238.     /**
  239.      * Where to find the source code and documentation - for informational
  240.      * purposes.
  241.      */
  242.     static final String sourceLocation =
  243.                    "http://java.sun.com/applets/applets/Animator/";
  244.  
  245.     /**
  246.      * Instructions to the user: how to produce the popup.
  247.      */
  248.     static final String userInstructions = "shift-click for errors, info";
  249.  
  250.     /**
  251.      * Applet info.
  252.      */
  253.     public String getAppletInfo() {
  254.     return "Animator v1.10 (02/05/97), by Herb Jellinek";
  255.     }
  256.  
  257.     /**
  258.      * Parameter info.
  259.      */
  260.     public String[][] getParameterInfo() {
  261.     String[][] info = {
  262.         {"imagesource",     "URL",         "a directory"},
  263.         {"startup",     "URL",         "image displayed at start-up"},
  264.         {"backgroundcolor", "int",          "color (24-bit RGB number) displayed as background"},
  265.         {"background",     "URL",         "image displayed as background"},
  266.         {"startimage",     "int",         "index of first image"},
  267.         {"endimage",     "int",         "index of last image"},
  268.         {"namepattern",     "URL",          "generates indexed names"},
  269.         {"images",          "URLs",         "list of image indices"},
  270.         {"href",        "URL",        "page to visit on mouse-click"},
  271.         {"target",        "name",        "frame to put that page in"},
  272.         {"pause",             "int",         "global pause, milliseconds"},
  273.         {"pauses",             "ints",     "individual pauses, milliseconds"},
  274.         {"repeat",             "boolean",     "repeat? true or false"},
  275.         {"positions",    "coordinates",     "path images will follow"},
  276.         {"soundsource",    "URL",         "audio directory"},
  277.         {"soundtrack",    "URL",         "background music"},
  278.         {"sounds",        "URLs",        "list of audio samples"},
  279.     };
  280.     return info;
  281.     }
  282.  
  283.     /**
  284.      * Show a crude "About" box.  Displays credits, errors (if any), and
  285.      * parameter values and documentation.
  286.      */
  287.     void showDescription() {
  288.     DescriptionFrame description = new DescriptionFrame();
  289.         
  290.     description.tell("\t\t"+getAppletInfo()+"\n");
  291.     description.tell("Updates, documentation at "+sourceLocation+"\n\n");
  292.     description.tell("Document base: "+getDocumentBase()+"\n");
  293.     description.tell("Code base: "+getCodeBase()+"\n\n");
  294.     
  295.     Object errors[] = tracker.getErrorsAny();
  296.     if (errors != null) {
  297.         description.tell("Applet image errors:\n");
  298.         for (int i = 0; i < errors.length; i++) {
  299.         if (errors[i] instanceof Image) {
  300.             URL url = (URL)imageNames.get((Image)errors[i]);
  301.             if (url != null) {
  302.             description.tell(" "+url+" not loaded\n");
  303.             }
  304.         }
  305.         }
  306.         description.tell("\n");
  307.     }
  308.     
  309.     if (images == null || images.size() == 0) {
  310.         description.tell("\n** No images loaded **\n\n");
  311.     }
  312.  
  313.     description.tell("Applet parameters:\n");
  314.     description.tell(" width = "+getParameter("WIDTH")+"\n");
  315.     description.tell(" height = "+getParameter("HEIGHT")+"\n");
  316.     
  317.     String params[][] = getParameterInfo();
  318.     for (int i = 0; i < params.length; i++) {
  319.         String name = params[i][0];
  320.         description.tell(" "+name+" = "+getParameter(name)+
  321.                  "\t ["+params[i][2]+"]\n");
  322.     }
  323.     
  324.     description.show();
  325.     }
  326.  
  327.     /**
  328.      * Print silly debugging info to the standard output.
  329.      */
  330.     void dbg(String s) {
  331.     if (debug) {
  332.         System.out.println("> "+s);
  333.     }
  334.     }
  335.  
  336.     /**
  337.      * Local version of getParameter for debugging purposes.
  338.      */
  339.     public String getParam(String key) {
  340.     String result = getParameter(key);
  341.     dbg("getParameter("+key+") = "+result);
  342.     return result;
  343.     }
  344.  
  345.     final int setFrameNum(int newFrameNum) {
  346.     frameNumKey = new Integer(frameNum = newFrameNum);
  347.     return frameNum;
  348.     }
  349.     
  350.     /**
  351.      * Parse the IMAGES parameter.  It looks like
  352.      * 1|2|3|4|5, etc., where each number (item) names a source image.
  353.      *
  354.      * @return a Vector of (URL) image file names.
  355.      */
  356.     Vector parseImages(String attr, String pattern)
  357.     throws MalformedURLException {
  358.     if (pattern == null) {
  359.         pattern = "T%N.gif";
  360.     }
  361.     Vector result = new Vector(10);
  362.     for (int i = 0; i < attr.length(); ) {
  363.         int next = attr.indexOf('|', i);
  364.         if (next == -1) next = attr.length();
  365.         String file = attr.substring(i, next);
  366.         result.addElement(new URL(imageSource, doSubst(pattern, file)));
  367.         i = next + 1;
  368.     }
  369.     return result;
  370.     }
  371.  
  372.     /**
  373.      * Fetch an image and wait for it to come in.  Used to enforce a load
  374.      * order for background and startup images.
  375.      * Places URL and image in imageNames table.
  376.      */
  377.     Image fetchImageAndWait(URL imageURL, int trackerClass) 
  378.     throws InterruptedException {
  379.     Image image = getImage(imageURL);
  380.     tracker.addImage(image, trackerClass);
  381.     imageNames.put(image, imageURL);
  382.     tracker.waitForID(trackerClass);
  383.     return image;
  384.     }
  385.  
  386.     /**
  387.      * Fetch the images named in the argument.  Is restartable.
  388.      *
  389.      * @param images a Vector of URLs
  390.      * @return true if all went well, false otherwise.
  391.      */
  392.     boolean fetchImages(Vector images) {
  393.     if (images == null) {
  394.         return true;
  395.     }
  396.  
  397.     int size = images.size();
  398.     for (int i = 0; i < size; i++) {
  399.         Object o = images.elementAt(i);
  400.         if (o instanceof URL) {
  401.         URL url = (URL)o;
  402.         tellLoadingMsg(url, imageLabel);
  403.         Image im = getImage(url);
  404.         tracker.addImage(im, ANIMATION_ID);
  405.         images.setElementAt(im, i);
  406.         imageNames.put(im, url);
  407.         }
  408.     }
  409.  
  410.     try {
  411.         tracker.waitForID(ANIMATION_ID);
  412.     } catch (InterruptedException e) {}
  413.     if (tracker.isErrorID(ANIMATION_ID)) {
  414.         return false;
  415.     }
  416.  
  417.     return true;
  418.     }
  419.  
  420.     /**
  421.      * Parse the SOUNDS parameter.  It looks like
  422.      * train.au||hello.au||stop.au, etc., where each item refers to a
  423.      * source image.  Empty items mean that the corresponding image
  424.      * has no associated sound.
  425.      *
  426.      * @return a Hashtable of SoundClips keyed to Integer frame numbers.
  427.      */
  428.     Hashtable parseSounds(String attr, Vector images)
  429.     throws MalformedURLException {
  430.     Hashtable result = new Hashtable();
  431.  
  432.     int imageNum = 0;
  433.     int numImages = images.size();
  434.     for (int i = 0; i < attr.length(); ) {
  435.         if (imageNum >= numImages) break;
  436.         
  437.         int next = attr.indexOf('|', i);
  438.         if (next == -1) next = attr.length();
  439.         
  440.         String sound = attr.substring(i, next);
  441.         if (sound.length() != 0) {
  442.         result.put(new Integer(imageNum),
  443.                new URL(soundSource, sound));
  444.         }
  445.         i = next + 1;
  446.         imageNum++;
  447.     }
  448.  
  449.     return result;
  450.     }
  451.  
  452.     /**
  453.      * Fetch the sounds named in the argument.
  454.      * Is restartable.
  455.      *
  456.      * @return URL of the first bogus file we hit, null if OK.
  457.      */
  458.     URL fetchSounds(Hashtable sounds) {
  459.     for (Enumeration e = sounds.keys() ; e.hasMoreElements() ;) {
  460.         Integer num = (Integer)e.nextElement();
  461.         Object o = sounds.get(num);
  462.         if (o instanceof URL) {
  463.         URL file = (URL)o;
  464.         tellLoadingMsg(file, soundLabel);
  465.         try {
  466.             sounds.put(num, getAudioClip(file));
  467.         } catch (Exception ex) {
  468.             return file;
  469.         }
  470.         }
  471.     }
  472.     return null;
  473.     }
  474.  
  475.     /**
  476.      * Parse the PAUSES parameter.  It looks like
  477.      * 1000|500|||750, etc., where each item corresponds to a
  478.      * source image.  Empty items mean that the corresponding image
  479.      * has no special duration, and should use the global one.
  480.      *
  481.      * @return a Hashtable of Integer pauses keyed to Integer
  482.      * frame numbers.
  483.      */
  484.     Hashtable parseDurations(String attr, Vector images) {
  485.     Hashtable result = new Hashtable();
  486.  
  487.     int imageNum = 0;
  488.     int numImages = images.size();
  489.     for (int i = 0; i < attr.length(); ) {
  490.         if (imageNum >= numImages) break;
  491.         
  492.         int next = attr.indexOf('|', i);
  493.         if (next == -1) next = attr.length();
  494.  
  495.         if (i != next) {
  496.         int duration = Integer.parseInt(attr.substring(i, next));
  497.         result.put(new Integer(imageNum), new Integer(duration));
  498.         } else {
  499.         result.put(new Integer(imageNum),
  500.                new Integer(globalPause));
  501.         }
  502.         i = next + 1;
  503.         imageNum++;
  504.     }
  505.  
  506.     return result;
  507.     }
  508.  
  509.     /**
  510.      * Parse a String of form xxx@yyy and return a Point.
  511.      */
  512.     Point parsePoint(String s) throws ParseException {
  513.     int atPos = s.indexOf('@');
  514.     if (atPos == -1) throw new ParseException("Illegal position: "+s);
  515.     return new Point(Integer.parseInt(s.substring(0, atPos)),
  516.              Integer.parseInt(s.substring(atPos + 1)));
  517.     }
  518.  
  519.  
  520.     /**
  521.      * Parse the POSITIONS parameter.  It looks like
  522.      * 10@30|11@31|||12@20, etc., where each item is an X@Y coordinate
  523.      * corresponding to a source image.  Empty items mean that the
  524.      * corresponding image has the same position as the preceding one.
  525.      *
  526.      * @return a Hashtable of Points keyed to Integer frame numbers.
  527.      */
  528.     Hashtable parsePositions(String param, Vector images)
  529.     throws ParseException {
  530.     Hashtable result = new Hashtable();
  531.  
  532.     int imageNum = 0;
  533.     int numImages = images.size();
  534.     for (int i = 0; i < param.length(); ) {
  535.         if (imageNum >= numImages) break;
  536.         
  537.         int next = param.indexOf('|', i);
  538.         if (next == -1) next = param.length();
  539.  
  540.         if (i != next) {
  541.         result.put(new Integer(imageNum),
  542.                parsePoint(param.substring(i, next)));
  543.         }
  544.         i = next + 1;
  545.         imageNum++;
  546.     }
  547.  
  548.     return result;
  549.     }
  550.     
  551.     /**
  552.      * Get the dimensions of an image.
  553.      * @return the image's dimensions.
  554.      */
  555.     Dimension getImageDimensions(Image im) {
  556.     return new Dimension(im.getWidth(null), im.getHeight(null));
  557.     }
  558.  
  559.     /**
  560.      * Substitute an integer some number of times in a string, subject to
  561.      * parameter strings embedded in the string.
  562.      * Parameter strings:
  563.      *   %N - substitute the integer as is, with no padding.
  564.      *   %<digit>, for example %5 - substitute the integer left-padded with
  565.      *        zeros to <digits> digits wide.
  566.      *   %% - substitute a '%' here.
  567.      * @param inStr the String to substitute within
  568.      * @param theInt the int to substitute, as a String.
  569.      */
  570.     String doSubst(String inStr, String theInt) {
  571.     String padStr = "0000000000";
  572.     int length = inStr.length();
  573.     StringBuffer result = new StringBuffer(length);
  574.     
  575.     for (int i = 0; i < length;) {
  576.         char ch = inStr.charAt(i);
  577.         if (ch == '%') {
  578.         i++;
  579.         if (i == length) {
  580.             result.append(ch);
  581.         } else {
  582.             ch = inStr.charAt(i);
  583.             if (ch == 'N' || ch == 'n') {
  584.             // just stick in the number, unmolested
  585.             result.append(theInt);
  586.             i++;
  587.             } else {
  588.             int pad;
  589.             if ((pad = Character.digit(ch, 10)) != -1) {
  590.                 // we've got a width value
  591.                 String numStr = theInt;
  592.                 String scr = padStr+numStr;
  593.                 result.append(scr.substring(scr.length() - pad));
  594.                 i++;
  595.             } else {
  596.                 result.append(ch);
  597.                 i++;
  598.             }
  599.             }
  600.         }
  601.         } else {
  602.         result.append(ch);
  603.         i++;
  604.         }
  605.     }
  606.     return result.toString();
  607.     }    
  608.  
  609.     /**
  610.      * Stuff a range of image names into a Vector.
  611.      *
  612.      * @return a Vector of image URLs.
  613.      */
  614.     Vector prepareImageRange(int startImage, int endImage, String pattern)
  615.     throws MalformedURLException {
  616.     Vector result = new Vector(Math.abs(endImage - startImage) + 1);
  617.     if (pattern == null) {
  618.         pattern = "T%N.gif";
  619.     }
  620.     if (startImage > endImage) {
  621.         for (int i = startImage; i >= endImage; i--) {
  622.         result.addElement(new URL(imageSource, doSubst(pattern, i+"")));
  623.         }
  624.     } else {
  625.         for (int i = startImage; i <= endImage; i++) {
  626.         result.addElement(new URL(imageSource, doSubst(pattern, i+"")));
  627.         }
  628.     }
  629.  
  630.     return result;
  631.     }
  632.  
  633.     
  634.     /**
  635.      * Initialize the applet.  Get parameters.
  636.      */
  637.     public void init() {
  638.  
  639.  
  640.     tracker = new MediaTracker(this);
  641.  
  642.     appWidth = getSize().width;
  643.     appHeight = getSize().height;
  644.  
  645.     try {
  646.         String param = getParam("IMAGESOURCE");    
  647.         imageSource = (param == null) ? getDocumentBase() : new URL(getDocumentBase(), param + "/");
  648.     
  649.         String href = getParam("HREF");
  650.         if (href != null) {
  651.         try {
  652.             hrefURL = new URL(getDocumentBase(), href);
  653.         } catch (MalformedURLException e) {
  654.             showParseError(e);
  655.         }
  656.         }
  657.  
  658.         hrefTarget = getParam("TARGET");
  659.         if (hrefTarget == null) {
  660.         hrefTarget = "_top";
  661.         }
  662.  
  663.         param = getParam("PAUSE");
  664.         globalPause =
  665.         (param != null) ? Integer.parseInt(param) : defaultPause;
  666.  
  667.         param = getParam("REPEAT");
  668.         repeat = (param == null) ? true : (param.equalsIgnoreCase("yes") ||
  669.                            param.equalsIgnoreCase("true"));
  670.  
  671.         int startImage = 1;
  672.         int endImage = 1;
  673.         param = getParam("ENDIMAGE");
  674.         if (param != null) {
  675.         endImage = Integer.parseInt(param);
  676.         param = getParam("STARTIMAGE");
  677.         if (param != null) {
  678.             startImage = Integer.parseInt(param);
  679.         }
  680.         param = getParam("NAMEPATTERN");
  681.         images = prepareImageRange(startImage, endImage, param);
  682.         } else {
  683.         param = getParam("STARTIMAGE");
  684.         if (param != null) {
  685.             startImage = Integer.parseInt(param);
  686.             param = getParam("NAMEPATTERN");
  687.             images = prepareImageRange(startImage, endImage, param);
  688.         } else {
  689.             param = getParam("IMAGES");
  690.             if (param == null) {
  691.             showStatus("No legal IMAGES, STARTIMAGE, or ENDIMAGE "+
  692.                    "specified.");
  693.             error = true;
  694.             return;
  695.             } else {
  696.             images = parseImages(param, getParam("NAMEPATTERN"));
  697.             }
  698.         }
  699.         }
  700.  
  701.         param = getParam("BACKGROUND");
  702.         if (param != null) {
  703.         backgroundImageURL = new URL(imageSource, param);
  704.         }
  705.  
  706.         param = getParam("BACKGROUNDCOLOR");
  707.         if (param != null) {
  708.         backgroundColor = decodeColor(param);
  709.         }
  710.  
  711.         param = getParam("STARTUP");
  712.         if (param != null) {
  713.         startUpImageURL = new URL(imageSource, param);
  714.         }
  715.  
  716.         param = getParam("SOUNDSOURCE");
  717.         soundSource = (param == null) ? imageSource : new URL(getDocumentBase(), param + "/");
  718.     
  719.         param = getParam("SOUNDS");
  720.         if (param != null) {
  721.         sounds = parseSounds(param, images);
  722.         }
  723.  
  724.         param = getParam("PAUSES");
  725.         if (param != null) {
  726.         durations = parseDurations(param, images);
  727.         }
  728.  
  729.         param = getParam("POSITIONS");
  730.         if (param != null) {
  731.         positions = parsePositions(param, images);
  732.         }
  733.  
  734.         param = getParam("SOUNDTRACK");
  735.         if (param != null) {
  736.         soundtrackURL = new URL(soundSource, param);
  737.         }
  738.     } catch (MalformedURLException e) {
  739.         showParseError(e);
  740.     } catch (ParseException e) {
  741.         showParseError(e);
  742.     }
  743.  
  744.     setFrameNum(0);
  745.     addMouseListener(this);
  746.     }
  747.  
  748.     Color decodeColor(String s) {
  749.     int val = 0;
  750.     try {
  751.         if (s.startsWith("0x")) {
  752.         val = Integer.parseInt(s.substring(2), 16);
  753.         } else if (s.startsWith("#")) {
  754.         val = Integer.parseInt(s.substring(1), 16);
  755.         } else if (s.startsWith("0") && s.length() > 1) {
  756.         val = Integer.parseInt(s.substring(1), 8);
  757.         } else {
  758.         val = Integer.parseInt(s, 10);
  759.         }
  760.         return new Color(val);
  761.     } catch (NumberFormatException e) {
  762.         return null;
  763.     }
  764.     }
  765.  
  766.     void tellLoadingMsg(String file, String fileType) {
  767.     showStatus("Animator: loading "+fileType+" "+file);
  768.     }
  769.  
  770.     void tellLoadingMsg(URL url, String fileType) {
  771.     tellLoadingMsg(url.toExternalForm(), fileType);
  772.     }
  773.  
  774.     void clearLoadingMessage() {
  775.     showStatus("");
  776.     }
  777.     
  778.     void loadError(String fileName, String fileType) {
  779.     String errorMsg = "Animator: Couldn't load "+fileType+" "+
  780.         fileName;
  781.     showStatus(errorMsg);
  782.     System.err.println(errorMsg);
  783.     error = true;
  784.     repaint();
  785.     }
  786.  
  787.     void loadError(URL badURL, String fileType) {
  788.     loadError(badURL.toExternalForm(), fileType);
  789.     }
  790.  
  791.     void loadErrors(Object errors[], String fileType) {
  792.     for (int i = 0; i < errors.length; i++) {
  793.         if (errors[i] instanceof Image) {
  794.         URL url = (URL)imageNames.get((Image)errors[i]);
  795.         if (url != null) {
  796.             loadError(url, fileType);
  797.         }
  798.         }
  799.     }
  800.     }
  801.  
  802.     void showParseError(Exception e) {
  803.     String errorMsg = "Animator: Parse error: "+e;
  804.     showStatus(errorMsg);
  805.     System.err.println(errorMsg);
  806.     error = true;
  807.     repaint();
  808.     }
  809.  
  810.     void startPlaying() {
  811.     if (soundtrack != null) {
  812.         soundtrack.loop();
  813.     }
  814.     }
  815.  
  816.     void stopPlaying() {
  817.     if (soundtrack != null) {
  818.         soundtrack.stop();
  819.     }
  820.     }
  821.  
  822.     /**
  823.      * Run the animation. This method is called by class Thread.
  824.      * @see java.lang.Thread
  825.      */
  826.     public void run() {
  827.     Thread me = Thread.currentThread();
  828.     URL badURL;
  829.     
  830.     me.setPriority(Thread.MIN_PRIORITY);
  831.  
  832.     if (! loaded) {
  833.         try {
  834.         // ... to do a bunch of loading.
  835.         if (startUpImageURL != null) {
  836.             tellLoadingMsg(startUpImageURL, imageLabel);
  837.             startUpImage = fetchImageAndWait(startUpImageURL,
  838.                              STARTUP_ID);
  839.             if (tracker.isErrorID(STARTUP_ID)) {
  840.             loadError(startUpImageURL, "start-up image");
  841.             }
  842.             repaint();
  843.         }
  844.         
  845.         if (backgroundImageURL != null) {
  846.             tellLoadingMsg(backgroundImageURL, imageLabel);
  847.             backgroundImage = fetchImageAndWait(backgroundImageURL, 
  848.                             BACKGROUND_ID);
  849.             if (tracker.isErrorID(BACKGROUND_ID)) {
  850.             loadError(backgroundImageURL, "background image");
  851.             }
  852.             repaint();
  853.         }
  854.  
  855.         // Fetch the animation frames
  856.         if (!fetchImages(images)) {
  857.             // get images in error, tell user about first of them
  858.             Object errors[] = tracker.getErrorsAny();
  859.             loadErrors(errors, imageLabel);
  860.             return;
  861.         }
  862.  
  863.         if (soundtrackURL != null && soundtrack == null) {
  864.             tellLoadingMsg(soundtrackURL, imageLabel);
  865.             soundtrack = getAudioClip(soundtrackURL);
  866.             if (soundtrack == null) {
  867.             loadError(soundtrackURL, "soundtrack");
  868.             return;
  869.             }
  870.         }
  871.  
  872.         if (sounds != null) {
  873.             badURL = fetchSounds(sounds);
  874.             if (badURL != null) {
  875.             loadError(badURL, soundLabel);
  876.             return;
  877.             }
  878.         }
  879.  
  880.         clearLoadingMessage();
  881.  
  882.         offScrImage = createImage(appWidth, appHeight);
  883.         offScrGC = offScrImage.getGraphics();
  884.         offScrGC.setColor(Color.lightGray);
  885.  
  886.         loaded = true;
  887.         error = false;
  888.         } catch (Exception e) {
  889.         error = true;
  890.         e.printStackTrace();
  891.         }
  892.     }
  893.  
  894.     if (userPause) {
  895.         return;
  896.     }
  897.  
  898.     if (repeat || frameNum < images.size()) {
  899.         startPlaying();
  900.     }
  901.  
  902.     try {
  903.         if (images != null && images.size() > 1) {
  904.         while (appWidth > 0 && appHeight > 0 && engine == me) {
  905.             if (frameNum >= images.size()) {
  906.             if (!repeat) {
  907.                 return;
  908.             }
  909.             setFrameNum(0);
  910.             }
  911.             repaint();
  912.  
  913.             if (sounds != null) {
  914.             AudioClip clip =
  915.                 (AudioClip)sounds.get(frameNumKey);
  916.             if (clip != null) {
  917.                 clip.play();
  918.             }
  919.             }
  920.  
  921.             try {
  922.             Integer pause = null;
  923.             if (durations != null) {
  924.                 pause = (Integer)durations.get(frameNumKey);
  925.             }
  926.             if (pause == null) {
  927.                 Thread.sleep(globalPause);
  928.             } else {
  929.                 Thread.sleep(pause.intValue());
  930.             }
  931.             } catch (InterruptedException e) {
  932.             // Should we do anything?
  933.             }
  934.             setFrameNum(frameNum+1);
  935.         }
  936.         }
  937.     } finally {
  938.         stopPlaying();
  939.     }
  940.     }
  941.  
  942.     /**
  943.      * No need to clear anything; just paint.
  944.      */
  945.     public void update(Graphics g) {
  946.     paint(g);
  947.     }
  948.  
  949.     /**
  950.      * Paint the current frame.
  951.      */
  952.     public void paint(Graphics g) {
  953.     if (error || !loaded) {
  954.         if (startUpImage != null) {
  955.         if (tracker.checkID(STARTUP_ID)) {
  956.             if (backgroundColor != null) {
  957.             g.setColor(backgroundColor);
  958.             g.fillRect(0, 0, appWidth, appHeight);
  959.             }
  960.             g.drawImage(startUpImage, 0, 0, this);
  961.         }
  962.         } else {
  963.         if (backgroundImage != null) {
  964.             if (tracker.checkID(BACKGROUND_ID)) {
  965.             g.drawImage(backgroundImage, 0, 0, this);
  966.             }
  967.         } else {
  968.             g.clearRect(0, 0, appWidth, appHeight);
  969.         }
  970.         }
  971.     } else {
  972.         if ((images != null) && (images.size() > 0) &&
  973.         tracker.checkID(ANIMATION_ID)) {
  974.         if (frameNum < images.size()) {
  975.             if (backgroundImage == null) {
  976.             offScrGC.clearRect(0, 0, appWidth, appHeight);
  977.             } else {
  978.             offScrGC.drawImage(backgroundImage, 0, 0, this);
  979.             }
  980.  
  981.             Image image = (Image)images.elementAt(frameNum);
  982.             
  983.             Point pos = null;
  984.             if (positions != null) {
  985.             pos = (Point)positions.get(frameNumKey);
  986.             }
  987.             if (pos != null) {
  988.             xPos = pos.x;
  989.             yPos = pos.y;
  990.             }
  991.             if (backgroundColor != null) {
  992.             offScrGC.setColor(backgroundColor);
  993.             offScrGC.fillRect(0, 0, appWidth, appHeight);
  994.             offScrGC.drawImage(image, xPos, yPos, backgroundColor,
  995.                        this);
  996.             } else {
  997.             offScrGC.drawImage(image, xPos, yPos, this);
  998.             }
  999.             g.drawImage(offScrImage, 0, 0, this);
  1000.         } else {
  1001.             // no more animation, but need to draw something
  1002.             dbg("No more animation; drawing last image.");
  1003.             if (backgroundImage == null) {
  1004.             g.fillRect(0, 0, appWidth, appHeight);
  1005.             } else {
  1006.             g.drawImage(backgroundImage, 0, 0, this);
  1007.             }
  1008.             g.drawImage((Image)images.lastElement(), 0, 0, this);
  1009.         }
  1010.         }
  1011.     }
  1012.     }
  1013.  
  1014.     /**
  1015.      * Start the applet by forking an animation thread.
  1016.      */
  1017.     public void start() {
  1018.     if (engine == null) {
  1019.         engine = new Thread(this);
  1020.         engine.start();
  1021.     }
  1022.     showStatus(getAppletInfo());
  1023.     }
  1024.  
  1025.     /**
  1026.      * Stop the insanity, um, applet.
  1027.      */
  1028.     public void stop() {
  1029.     if (engine != null && engine.isAlive()) {
  1030.         engine.stop();
  1031.     }
  1032.     engine = null;
  1033.     }
  1034.  
  1035.     /**
  1036.      * Pause the thread when the user clicks the mouse in the applet.
  1037.      * If the thread has stopped (as in a non-repeat performance),
  1038.      * restart it.
  1039.      */
  1040.     public void mousePressed(MouseEvent event) {
  1041.     if ((event.getModifiers() & InputEvent.SHIFT_MASK) != 0) {
  1042.         showDescription();
  1043.         return;
  1044.     } else if (hrefURL != null) {
  1045.         //Let mouseClicked handle this.
  1046.         return;
  1047.         } else if (loaded) {
  1048.         if (engine != null && engine.isAlive()) {
  1049.         if (userPause) {
  1050.             engine.resume();
  1051.             startPlaying();
  1052.             } else {
  1053.                 engine.suspend();
  1054.                 stopPlaying();
  1055.             }
  1056.             userPause = !userPause;
  1057.         } else {
  1058.             userPause = false;
  1059.             setFrameNum(0);
  1060.             engine = new Thread(this);
  1061.             engine.start();
  1062.         }
  1063.     }
  1064.     }
  1065.  
  1066.     public void mouseClicked(MouseEvent event) {
  1067.     if ((hrefURL != null) &&
  1068.             ((event.getModifiers() & InputEvent.SHIFT_MASK) == 0)) {
  1069.         getAppletContext().showDocument(hrefURL, hrefTarget);
  1070.     }
  1071.     }
  1072.  
  1073.     public void mouseReleased(MouseEvent event) {
  1074.     }
  1075.  
  1076.     public void mouseEntered(MouseEvent event) {
  1077.     showStatus(getAppletInfo()+" -- "+userInstructions);
  1078.     }
  1079.  
  1080.     public void mouseExited(MouseEvent event) {
  1081.         showStatus("");
  1082.     }
  1083.     
  1084. }
  1085.  
  1086.  
  1087. /**
  1088.  * ParseException: signals a parameter parsing problem.
  1089.  */
  1090. class ParseException extends Exception {
  1091.     ParseException(String s) {
  1092.     super(s);
  1093.     }
  1094. }
  1095.  
  1096.  
  1097. /**
  1098.  * DescriptionFrame: implements a pop-up "About" box.
  1099.  */
  1100. class DescriptionFrame extends Frame 
  1101.                implements ActionListener {
  1102.  
  1103.     static final int rows = 27;
  1104.     static final int cols = 70;
  1105.     
  1106.     TextArea info;
  1107.     Button cancel;
  1108.  
  1109.     DescriptionFrame() {
  1110.     super("Animator v1.10");
  1111.     add("Center", info = new TextArea(rows, cols));
  1112.     info.setEditable(false);
  1113.     info.setBackground(Color.white);
  1114.     Panel buttons = new Panel();
  1115.     add("South", buttons);
  1116.     buttons.add(cancel = new Button("Cancel"));
  1117.     cancel.addActionListener(this);
  1118.     
  1119.     pack();
  1120.     }
  1121.  
  1122.     public void show() {
  1123.     info.select(0,0);
  1124.     super.show();
  1125.     }
  1126.  
  1127.     void tell(String s) {
  1128.     info.append(s);
  1129.     }
  1130.  
  1131.     public void actionPerformed(ActionEvent e) {
  1132.     setVisible(false);
  1133.     }
  1134. }
  1135.